// DVC error handling and recovery tests
// Tests various error scenarios and recovery mechanisms

use serde_json::Value;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
use tempfile::TempDir;
use tokio::time::timeout;

use axum::{
    extract::DefaultBodyLimit,
    http::Method,
    routing::{delete, get, post, put},
    Router,
};
use rdm_rust::config::AppConfig;
use rdm_rust::database::connection::establish_connection_pool;
use rdm_rust::handlers::{auth, data, dvc_remote};
use rdm_rust::services::{AuthService, DataService, DvcService, StorageService};
use rdm_rust::AppState;
use tower::ServiceBuilder;
use tower_http::{
    cors::{Any, CorsLayer},
    trace::TraceLayer,
};

// Test configuration
const TEST_SERVER_PORT: u16 = 0; // Use random available port
const TEST_TIMEOUT: Duration = Duration::from_secs(30);

struct ErrorHandlingTestServer {
    port: u16,
    temp_dir: TempDir,
    base_url: String,
    client: reqwest::Client,
}

impl Clone for ErrorHandlingTestServer {
    fn clone(&self) -> Self {
        Self {
            port: self.port,
            temp_dir: TempDir::new().unwrap(), // Create new temp dir
            base_url: self.base_url.clone(),
            client: self.client.clone(),
        }
    }
}

impl ErrorHandlingTestServer {
    async fn new() -> Self {
        // Create temporary directory for test storage
        let temp_dir = TempDir::new().expect("Failed to create temp dir");

        // Load test configuration
        let mut config = AppConfig::from_env().expect("Failed to load config");
        config.server.port = TEST_SERVER_PORT;
        config.dvc.storage_path = temp_dir.path().to_string_lossy().to_string();

        // Initialize database
        let db_pool = establish_connection_pool(&config.database.url)
            .await
            .expect("Failed to establish database connection");

        // Initialize services
        let auth_service = Arc::new(AuthService::new(db_pool.clone(), config.clone()));
        let data_service = Arc::new(DataService::new(db_pool.clone()));
        let storage_service = Arc::new(StorageService::new(config.dvc.storage_path.clone()));
        let dvc_service = Arc::new(DvcService::new(db_pool.clone()));

        let app_state = AppState {
            auth_service,
            data_service,
            storage_service,
            dvc_service,
        };

        // Build application router
        let public_routes = Router::new()
            .route("/api/register", post(auth::register))
            .route("/api/register-admin", post(auth::register_admin))
            .route("/api/login", post(auth::login))
            .route("/api/data/public", get(data::list_public_data_items));

        let protected_routes = Router::new()
            .route("/api/user-info", get(auth::user_info))
            .route("/api/data", get(data::list_data_items))
            .route("/api/data/upload", post(data::upload_data))
            .route("/api/data/:id", get(data::get_data_item))
            .route("/api/data/:id", delete(data::delete_data_item))
            .route("/api/data/:id/download", get(data::download_data_file))
            .route("/api/data/:id/dvc", get(data::download_dvc_file))
            .route("/api/data/:id/dvc/content", get(data::get_dvc_content))
            .route("/api/data/:id/metadata", put(data::upload_metadata))
            .route("/api/dvc/files", get(dvc_remote::dvc_list_files))
            .route("/api/dvc/file/*path", get(dvc_remote::dvc_get_file_info))
            .route(
                "/api/dvc/remote/*path",
                axum::routing::any(dvc_remote::handle_dvc_request),
            )
            .route_layer(axum::middleware::from_fn_with_state(
                app_state.clone(),
                rdm_rust::middleware::auth::auth_middleware,
            ));

        let app = Router::new()
            .merge(public_routes)
            .merge(protected_routes)
            .layer(
                ServiceBuilder::new()
                    .layer(TraceLayer::new_for_http())
                    .layer(
                        CorsLayer::new()
                            .allow_origin(Any)
                            .allow_methods([
                                Method::GET,
                                Method::POST,
                                Method::PUT,
                                Method::DELETE,
                                Method::OPTIONS,
                                Method::HEAD,
                            ])
                            .allow_headers(Any),
                    )
                    .layer(DefaultBodyLimit::max(100 * 1024 * 1024)), // 100MB max file size
            )
            .with_state(app_state);

        // Start server on random port
        let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
            .await
            .expect("Failed to bind to address");
        let port = listener
            .local_addr()
            .expect("Failed to get local address")
            .port();

        // Spawn server task
        tokio::spawn(async move {
            axum::serve(listener, app)
                .await
                .expect("Failed to serve server");
        });

        // Give server time to start
        tokio::time::sleep(Duration::from_millis(100)).await;

        let base_url = format!("http://127.0.0.1:{}", port);
        let client = reqwest::Client::builder()
            .timeout(TEST_TIMEOUT)
            .build()
            .expect("Failed to create HTTP client");

        Self {
            port,
            temp_dir,
            base_url,
            client,
        }
    }

    async fn wait_for_server(&self) -> Result<(), Box<dyn std::error::Error>> {
        let health_url = format!("{}/api/data/public", self.base_url);

        for _ in 0..10 {
            match timeout(Duration::from_secs(1), self.client.get(&health_url).send()).await {
                Ok(Ok(response)) if response.status().is_success() => return Ok(()),
                Ok(_) => (),
                Err(_) => (),
            }
            tokio::time::sleep(Duration::from_millis(100)).await;
        }

        Err("Server failed to start".into())
    }

    async fn register_and_login(&self, username: &str, password: &str) -> String {
        // Register user
        let email = format!("{}@hillstonenet.com", username);
        let register_response = self
            .client
            .post(format!("{}/api/register", self.base_url))
            .json(&serde_json::json!({
                "email": email,
                "password": password
            }))
            .send()
            .await
            .expect("Failed to send register request");

        assert_eq!(
            register_response.status(),
            201,
            "User registration should succeed"
        );

        // Login
        let login_response = self
            .client
            .post(format!("{}/api/login", self.base_url))
            .json(&serde_json::json!({
                "email": email,
                "password": password
            }))
            .send()
            .await
            .expect("Failed to send login request");

        assert_eq!(login_response.status(), 200, "Login should succeed");

        let login_body: Value = login_response
            .json()
            .await
            .expect("Failed to parse login response");
        login_body
            .get("access_token")
            .or_else(|| login_body.get("token"))
            .and_then(|t| t.as_str())
            .expect("Login response should contain token")
            .to_string()
    }
}

#[tokio::test]
async fn test_dvc_invalid_file_paths() {
    let server = ErrorHandlingTestServer::new().await;
    server
        .wait_for_server()
        .await
        .expect("Test server failed to start");

    let token = server
        .register_and_login("path_test_user", "password123")
        .await;

    // Test various invalid file paths
    let invalid_paths = vec![
        "",                            // Empty path
        "/",                           // Root path
        "//double//slash",             // Double slashes
        "../parent.txt",               // Parent directory traversal
        "../../etc/passwd",            // Deep parent traversal
        "/absolute/path.txt",          // Absolute path
        "path/with spaces/file.txt",   // Path with spaces
        "path/with\nnewline/file.txt", // Path with newline
        "path/with\ttab/file.txt",     // Path with tab
    ];

    for invalid_path in &invalid_paths {
        let dvc_put_response = server
            .client
            .put(format!(
                "{}/api/dvc/remote/{}",
                server.base_url, invalid_path
            ))
            .header("Authorization", format!("Bearer {}", token))
            .header("Content-Type", "application/octet-stream")
            .body(b"test content".to_vec())
            .send()
            .await
            .expect("Failed to send DVC PUT request");

        // Should either succeed (if path is valid) or fail gracefully
        if !invalid_path.is_empty()
            && !invalid_path.starts_with('/')
            && !invalid_path.contains("..")
            && !invalid_path.contains('\n')
            && !invalid_path.contains('\t')
        {
            // These might be valid paths (double slashes get normalized)
            if dvc_put_response.status().is_success() {
                println!("Path '{}' was accepted (may be valid)", invalid_path);
            } else {
                println!(
                    "Path '{}' was rejected with status: {}",
                    invalid_path,
                    dvc_put_response.status()
                );
            }
        } else {
            // These should definitely be rejected
            assert!(
                !dvc_put_response.status().is_success(),
                "Invalid path '{}' should be rejected",
                invalid_path
            );
        }
    }

    println!("Completed invalid file path tests");
}

#[tokio::test]
async fn test_dvc_empty_file_handling() {
    let server = ErrorHandlingTestServer::new().await;
    server
        .wait_for_server()
        .await
        .expect("Test server failed to start");

    let token = server
        .register_and_login("empty_file_user", "password123")
        .await;

    // Test empty file upload
    let empty_file_path = "data/empty_file.txt";
    let empty_content = b"";

    let dvc_put_response = server
        .client
        .put(format!(
            "{}/api/dvc/remote/{}",
            server.base_url, empty_file_path
        ))
        .header("Authorization", format!("Bearer {}", token))
        .header("Content-Type", "application/octet-stream")
        .body(empty_content.to_vec())
        .send()
        .await
        .expect("Failed to send DVC PUT request");

    // Empty files should be rejected
    assert_eq!(
        dvc_put_response.status(),
        400,
        "Empty file upload should be rejected"
    );

    let error_body: Value = dvc_put_response
        .json()
        .await
        .expect("Failed to parse error response");
    let error_detail = error_body.get("detail").unwrap().as_str().unwrap();
    assert!(
        error_detail.contains("empty") || error_detail.contains("content"),
        "Error should mention empty content: {}",
        error_detail
    );

    println!("Correctly rejected empty file upload");
}

#[tokio::test]
async fn test_dvc_oversized_file_handling() {
    let server = ErrorHandlingTestServer::new().await;
    server
        .wait_for_server()
        .await
        .expect("Test server failed to start");

    let token = server
        .register_and_login("oversized_user", "password123")
        .await;

    // Create a file larger than the server limit (100MB)
    // We'll create a 150MB file to test the limit
    let oversized_content = vec![0u8; 150 * 1024 * 1024];
    let oversized_file_path = "data/oversized_file.bin";

    let dvc_put_response = server
        .client
        .put(format!(
            "{}/api/dvc/remote/{}",
            server.base_url, oversized_file_path
        ))
        .header("Authorization", format!("Bearer {}", token))
        .header("Content-Type", "application/octet-stream")
        .body(oversized_content)
        .send()
        .await
        .expect("Failed to send DVC PUT request");

    // Oversized files should be rejected
    assert!(
        !dvc_put_response.status().is_success(),
        "Oversized file should be rejected"
    );

    println!(
        "Correctly rejected oversized file upload with status: {}",
        dvc_put_response.status()
    );
}

#[tokio::test]
async fn test_dvc_nonexistent_file_operations() {
    let server = ErrorHandlingTestServer::new().await;
    server
        .wait_for_server()
        .await
        .expect("Test server failed to start");

    let token = server
        .register_and_login("nonexistent_user", "password123")
        .await;

    let nonexistent_path = "data/nonexistent_file.txt";

    // Test GET on nonexistent file
    let dvc_get_response = server
        .client
        .get(format!(
            "{}/api/dvc/remote/{}",
            server.base_url, nonexistent_path
        ))
        .header("Authorization", format!("Bearer {}", token))
        .send()
        .await
        .expect("Failed to send DVC GET request");

    assert_eq!(
        dvc_get_response.status(),
        404,
        "GET on nonexistent file should return 404"
    );

    // Test HEAD on nonexistent file
    let dvc_head_response = server
        .client
        .head(format!(
            "{}/api/dvc/remote/{}",
            server.base_url, nonexistent_path
        ))
        .header("Authorization", format!("Bearer {}", token))
        .send()
        .await
        .expect("Failed to send DVC HEAD request");

    assert_eq!(
        dvc_head_response.status(),
        404,
        "HEAD on nonexistent file should return 404"
    );

    // Test DELETE on nonexistent file
    let dvc_delete_response = server
        .client
        .delete(format!(
            "{}/api/dvc/remote/{}",
            server.base_url, nonexistent_path
        ))
        .header("Authorization", format!("Bearer {}", token))
        .send()
        .await
        .expect("Failed to send DVC DELETE request");

    assert_eq!(
        dvc_delete_response.status(),
        404,
        "DELETE on nonexistent file should return 404"
    );

    // Test file info on nonexistent file
    let dvc_info_response = server
        .client
        .get(format!(
            "{}/api/dvc/file/{}",
            server.base_url, nonexistent_path
        ))
        .header("Authorization", format!("Bearer {}", token))
        .send()
        .await
        .expect("Failed to send DVC info request");

    assert_eq!(
        dvc_info_response.status(),
        404,
        "File info on nonexistent file should return 404"
    );

    println!("Correctly handled operations on nonexistent files");
}

#[tokio::test]
async fn test_dvc_malformed_requests() {
    let server = ErrorHandlingTestServer::new().await;
    server
        .wait_for_server()
        .await
        .expect("Test server failed to start");

    let token = server
        .register_and_login("malformed_user", "password123")
        .await;

    // Test PUT with invalid content type
    let dvc_put_invalid_content_type = server
        .client
        .put(format!("{}/api/dvc/remote/data/test.txt", server.base_url))
        .header("Authorization", format!("Bearer {}", token))
        .header("Content-Type", "text/html") // Wrong content type
        .body(b"test content".to_vec())
        .send()
        .await
        .expect("Failed to send DVC PUT request");

    // Should still work since we don't strictly enforce content type
    assert!(
        dvc_put_invalid_content_type.status().is_success()
            || dvc_put_invalid_content_type.status() == 400,
        "PUT with wrong content type should either succeed or be rejected gracefully"
    );

    // Test PUT without content
    let dvc_put_no_content = server
        .client
        .put(format!("{}/api/dvc/remote/data/test2.txt", server.base_url))
        .header("Authorization", format!("Bearer {}", token))
        .send()
        .await
        .expect("Failed to send DVC PUT request");

    assert!(
        !dvc_put_no_content.status().is_success(),
        "PUT without content should be rejected"
    );

    // Test with malformed JSON in Authorization header
    let dvc_malformed_auth = server
        .client
        .get(format!("{}/api/dvc/files", server.base_url))
        .header("Authorization", "Bearer invalid.jwt.token") // Invalid JWT
        .send()
        .await
        .expect("Failed to send DVC list request");

    assert_eq!(
        dvc_malformed_auth.status(),
        401,
        "Malformed auth should be rejected"
    );

    println!("Correctly handled malformed requests");
}

#[tokio::test]
async fn test_dvc_file_overwrite_scenarios() {
    let server = ErrorHandlingTestServer::new().await;
    server
        .wait_for_server()
        .await
        .expect("Test server failed to start");

    let token = server
        .register_and_login("overwrite_user", "password123")
        .await;

    let file_path = "data/overwrite_test.txt";
    let original_content = b"Original content";
    let updated_content = b"Updated content";

    // First upload
    let dvc_put_first = server
        .client
        .put(format!("{}/api/dvc/remote/{}", server.base_url, file_path))
        .header("Authorization", format!("Bearer {}", token))
        .header("Content-Type", "application/octet-stream")
        .body(original_content.to_vec())
        .send()
        .await
        .expect("Failed to send first DVC PUT request");

    assert_eq!(dvc_put_first.status(), 200, "First upload should succeed");

    // Verify original content
    let dvc_get_original = server
        .client
        .get(format!("{}/api/dvc/remote/{}", server.base_url, file_path))
        .header("Authorization", format!("Bearer {}", token))
        .send()
        .await
        .expect("Failed to send DVC GET request");

    assert_eq!(dvc_get_original.status(), 200);
    let original_downloaded = dvc_get_original
        .bytes()
        .await
        .expect("Failed to download original");
    assert_eq!(original_downloaded.as_ref(), original_content);

    // Overwrite with new content
    let dvc_put_overwrite = server
        .client
        .put(format!("{}/api/dvc/remote/{}", server.base_url, file_path))
        .header("Authorization", format!("Bearer {}", token))
        .header("Content-Type", "application/octet-stream")
        .body(updated_content.to_vec())
        .send()
        .await
        .expect("Failed to send overwrite DVC PUT request");

    assert_eq!(dvc_put_overwrite.status(), 200, "Overwrite should succeed");

    // Verify updated content
    let dvc_get_updated = server
        .client
        .get(format!("{}/api/dvc/remote/{}", server.base_url, file_path))
        .header("Authorization", format!("Bearer {}", token))
        .send()
        .await
        .expect("Failed to send DVC GET request after overwrite");

    assert_eq!(dvc_get_updated.status(), 200);
    let updated_downloaded = dvc_get_updated
        .bytes()
        .await
        .expect("Failed to download updated");
    assert_eq!(updated_downloaded.as_ref(), updated_content);

    // Verify file info reflects the update
    let dvc_info_response = server
        .client
        .get(format!("{}/api/dvc/file/{}", server.base_url, file_path))
        .header("Authorization", format!("Bearer {}", token))
        .send()
        .await
        .expect("Failed to send DVC info request");

    assert_eq!(dvc_info_response.status(), 200);

    let dvc_info_body: Value = dvc_info_response
        .json()
        .await
        .expect("Failed to parse DVC info response");
    assert_eq!(
        dvc_info_body.get("size").unwrap().as_i64().unwrap(),
        updated_content.len() as i64
    );

    println!("Successfully tested file overwrite scenarios");
}

#[tokio::test]
async fn test_dvc_concurrent_same_file_operations() {
    let server = ErrorHandlingTestServer::new().await;
    server
        .wait_for_server()
        .await
        .expect("Test server failed to start");

    let token = server
        .register_and_login("concurrent_same_user", "password123")
        .await;

    let file_path = "data/concurrent_same_file.txt";

    // Launch multiple concurrent PUT operations on the same file
    let mut tasks = tokio::task::JoinSet::new();

    for i in 1..=10 {
        let content = format!("Content from operation {}", i);
        let server_clone = server.clone();
        let token = token.clone();
        let file_path = file_path.to_string();

        tasks.spawn(async move {
            let response = server_clone
                .client
                .put(format!(
                    "{}/api/dvc/remote/{}",
                    server_clone.base_url, file_path
                ))
                .header("Authorization", format!("Bearer {}", token))
                .header("Content-Type", "application/octet-stream")
                .body(content.into_bytes())
                .send()
                .await
                .expect("Failed to send concurrent DVC PUT request");

            response.status()
        });
    }

    // Collect results
    let mut success_count = 0;
    let mut error_count = 0;

    while let Some(result) = tasks.join_next().await {
        let status = result.expect("Task should complete");
        if status.is_success() {
            success_count += 1;
        } else {
            error_count += 1;
        }
    }

    println!(
        "Concurrent same-file operations: {} successful, {} errors",
        success_count, error_count
    );

    // At least some operations should succeed
    assert!(
        success_count > 0,
        "At least some concurrent operations should succeed"
    );

    // Verify final file state
    let dvc_get_final = server
        .client
        .get(format!("{}/api/dvc/remote/{}", server.base_url, file_path))
        .header("Authorization", format!("Bearer {}", token))
        .send()
        .await
        .expect("Failed to send final DVC GET request");

    assert_eq!(
        dvc_get_final.status(),
        200,
        "Final file should be accessible"
    );

    let final_content = dvc_get_final
        .bytes()
        .await
        .expect("Failed to read final content");
    assert!(!final_content.is_empty(), "Final file should not be empty");

    println!("Final file content length: {} bytes", final_content.len());
}
